use rustler::{env, Atom, Encoder, Env, Error, MapIterator, NifResult, NifTuple, OwnedEnv, ResourceArc, Term};
use polyanya::{PolyanyaFile, Triangulation, Mesh};
use glam::{vec2, Vec2};

// docs: https://docs.rs/rustler/latest/rustler/index.html

mod atoms {
    rustler::atoms! {
        ok,
        open,
        close,
        element,
        empty,

        timestamp,
        int,
        float,
        string,
        list,

        done,
    }
}


#[derive(NifTuple)]
pub struct PosTuple {
    x: f32,
    y: f32,
}

/// 定义一个资源结构体来存储 `navmesh` 数据
pub struct NavmeshResource {
    pub mesh_data: Mesh,
}

impl NavmeshResource {
    pub fn new(mesh_data: Mesh) -> Self {
        NavmeshResource { mesh_data: mesh_data }
    }
}

#[rustler::nif]
fn find_path<'a>(env: Env<'a>, resource: ResourceArc<NavmeshResource>, from: PosTuple, to: PosTuple) -> Term<'a> {

    let mesh: &Mesh = &resource.mesh_data;
    let v2_from = vec2(from.x, from.y);
    let v2_to = vec2(to.x, to.y);

    let path = mesh.path(v2_from, v2_to);
    let v2_path = path.unwrap().path;
    vec2_list_to_erlang_tuple_list(v2_path, env)
}

fn vec2_list_to_erlang_tuple_list<'a>(vec2_list: Vec<Vec2>, env: Env<'a>) -> Term<'a> {
    let erlang_tuple_list: Vec<PosTuple> = vec2_list
        .iter()
        .map(|vec2| to_pos_tuple(vec2))
        .collect();
    // erlang_tuple_list.len().encode(env)
    erlang_tuple_list.encode(env)
}

fn to_pos_tuple(v2: &Vec2) -> PosTuple {
    PosTuple {x: v2.x, y: v2.y}
}

#[rustler::nif]
fn load_polyanya_navmesh(navmesh_file: String) -> ResourceArc<NavmeshResource> {
    let data: Mesh = PolyanyaFile::from_file(&navmesh_file)
                            .try_into()
                            .unwrap();
    ResourceArc::new(NavmeshResource::new(data))
}

#[rustler::nif]
fn load_unity_navmesh(navmesh_file: String) -> ResourceArc<NavmeshResource> {
    let mut file = std::fs::File::open(path).unwrap();
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer).unwrap();
    let data: Mesh = unity_navmesh_from_bytes(&buffer);
    ResourceArc::new(NavmeshResource::new(data))
}

fn unity_navmesh_from_bytes(bytes: &[u8]) -> Trimesh {
    let mut mesh = Trimesh {
        vertices: vec![],
        triangles: vec![],
    };
    let mut face: [i32; 32] = [0; 32];
    let mut m_vert_count:i32 = 0;
    for line in io::BufReader::new(bytes).lines() {
        let line: String = line.unwrap();
        if line[0] == ('#') {
                            continue;
                        }
        if line[0] == ('v') && line[1] != ('n') && line[1] != ('t'){
            // Vertex pos
            let mut values = line.split(' ');
            let x = values.next().unwrap().parse().unwrap();
            let y = values.next().unwrap().parse().unwrap();
            let _z = values.next().unwrap().parse().unwrap();
            let vertex = Vertex::new(
                Vec2::new(x, y),
                values.map(|v| v.parse().unwrap_or(u32::MAX)).collect(),
            );
            mesh.vertices.push(vertex);
            m_vert_count += 1;
        }
        if line[0] == ('f'){
            // Faces
            let nv = parse_unity_navmesh_face(line+1, face, 32, m_vert_count);
        }


            if phase == 0 {
                if line == "mesh" || line == "2" {
                    continue;
                } else {
                    (nb_vertices, nb_polygons) = line
                        .split_once(' ')
                        .map(|(a, b)| (a.parse().unwrap(), b.parse().unwrap()))
                        .unwrap();
                    phase = 1;
                    continue;
                }
            }
            if phase == 1 {
                if nb_vertices > 0 {
                    nb_vertices -= 1;
                    let mut values = line.split(' ');
                    let x = values.next().unwrap().parse().unwrap();
                    let y = values.next().unwrap().parse().unwrap();
                    let _ = values.next();
                    let vertex = Vertex::new(
                        Vec2::new(x, y),
                        values.map(|v| v.parse().unwrap_or(u32::MAX)).collect(),
                    );
                    mesh.vertices.push(vertex);
                } else {
                    phase = 2;
                }
            }
            if phase == 2 {
                if nb_polygons > 0 {
                    nb_polygons -= 1;
                    let mut values = line.split(' ');
                    let n = values.next().unwrap().parse().unwrap();
                    let polygon = Polygon::using(n, values.map(|v| v.parse().unwrap()).collect());
                    mesh.polygons.push(polygon)
                } else {
                    panic!("unexpected line");
                }
            }
        }
        mesh
    }

fn parse_unity_navmesh_face(row: &str, data: &mut [i32], n: usize, vcnt: i32) -> usize {
    let mut j = 0;
    for word in row.split_whitespace() {
        // Skip initial white space
        if word.is_empty() {
            continue;
        }

        // Find vertex delimiter and terminate the string there for conversion.
        let mut s = word;
        while s.chars().any(|c| c == '/' || c == '\t') {
            s = &s[..(s.find('/').unwrap_or(s.len())).min(s.find('\t').unwrap_or(s.len()))];
        }

        if s.is_empty() {
            continue;
        }

        let vi: i32 = s.parse().unwrap_or(0); // Use 0 as default value if parse fails
        data[j] = if vi < 0 { vi + vcnt } else { vi - 1 };
        j += 1;
        if j >= n {
            break;
        }
    }
    j
}

pub fn load(env: Env, _term: Term) -> bool {
    rustler::resource!(NavmeshResource, env);
    true
}

rustler::init!("polyanya_nif", load = load);
